package services

import (
	"bytes"
	"context"
	"fmt"
	"image"
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/disintegration/imaging"
	"github.com/google/uuid"

	"github.com/samaa/photosalesplus/backend/internal/config"
	"github.com/samaa/photosalesplus/backend/internal/models"
	"github.com/samaa/photosalesplus/backend/internal/repositories"
	"github.com/samaa/photosalesplus/backend/internal/storage"
)

const previewMaxWidth = 1280
const thumbMaxWidth = 320

type UploadService interface {
	SaveImage(ctx context.Context, file multipart.File, header *multipart.FileHeader, opts UploadOptions) (*models.MediaAsset, error)
}

type uploadService struct {
	cfg       *config.Config
	assetRepo repositories.MediaAssetRepository
}

type UploadOptions struct {
	MaxBytes     int64
	AllowedMIMEs []string
}

func NewUploadService(cfg *config.Config, repo repositories.MediaAssetRepository) UploadService {
	return &uploadService{
		cfg:       cfg,
		assetRepo: repo,
	}
}

func (s *uploadService) SaveImage(ctx context.Context, file multipart.File, header *multipart.FileHeader, opts UploadOptions) (*models.MediaAsset, error) {
	defer file.Close()

	maxBytes := opts.MaxBytes
	if maxBytes < 0 {
		maxBytes = 0
	}

	reader := io.Reader(file)
	if maxBytes > 0 {
		reader = io.LimitReader(file, maxBytes+1)
	}

	content, err := io.ReadAll(reader)
	if err != nil {
		return nil, err
	}

	if len(content) == 0 {
		return nil, fmt.Errorf("empty file payload")
	}

	if maxBytes > 0 && int64(len(content)) > maxBytes {
		return nil, fmt.Errorf("文件大小超出限制（最大 %.1f MB）", float64(maxBytes)/(1024*1024))
	}

	mimeType := normalizeMIME(header.Header.Get("Content-Type"))
	if mimeType == "" {
		mimeType = normalizeMIME(http.DetectContentType(content))
	}

	allowed := opts.AllowedMIMEs
	if len(allowed) == 0 {
		allowed = []string{"image/jpeg", "image/png", "image/gif"}
	}
	if !isAllowedMIME(mimeType, allowed) {
		return nil, fmt.Errorf("不支持的图片格式")
	}

	ext := strings.ToLower(filepath.Ext(header.Filename))
	if ext == "" {
		switch mimeType {
		case "image/png":
			ext = ".png"
		case "image/gif":
			ext = ".gif"
		default:
			ext = ".jpg"
		}
	}

	baseID := uuid.NewString()
	originalFilename := fmt.Sprintf("%s%s", baseID, ext)
	previewFilename := fmt.Sprintf("%s_preview.jpg", baseID)

	now := time.Now()
	originalRel := storage.BuildDatedRelativePath(storage.VariantOriginal, originalFilename, now)
	previewRel := storage.BuildDatedRelativePath(storage.VariantPreview, previewFilename, now)

	if err := storage.EnsureDirForRelative(s.cfg, originalRel); err != nil {
		return nil, err
	}
	if err := storage.EnsureDirForRelative(s.cfg, previewRel); err != nil {
		return nil, err
	}

	originalPath := storage.AbsolutePath(s.cfg, originalRel)
	if err := os.WriteFile(originalPath, content, 0o644); err != nil {
		return nil, err
	}

	cfg, _, err := image.DecodeConfig(bytes.NewReader(content))
	if err != nil {
		return nil, err
	}

	img, err := imaging.Decode(bytes.NewReader(content), imaging.AutoOrientation(true))
	if err != nil {
		return nil, err
	}

	previewImg := img
	if previewImg.Bounds().Dx() > previewMaxWidth {
		previewImg = imaging.Resize(previewImg, previewMaxWidth, 0, imaging.Lanczos)
	}

	previewPath := storage.AbsolutePath(s.cfg, previewRel)
	previewFile, err := os.Create(previewPath)
	if err != nil {
		return nil, err
	}

	if err := imaging.Encode(previewFile, previewImg, imaging.JPEG, imaging.JPEGQuality(82)); err != nil {
		previewFile.Close()
		return nil, err
	}

	if err := previewFile.Close(); err != nil {
		return nil, err
	}

	previewInfo, err := os.Stat(previewPath)
	if err != nil {
		return nil, err
	}

	thumbFilename := fmt.Sprintf("%s_thumb.jpg", baseID)
	thumbRel := storage.BuildDatedRelativePath(storage.VariantPreview, thumbFilename, now)
	if err := storage.EnsureDirForRelative(s.cfg, thumbRel); err != nil {
		return nil, err
	}
	thumbPath := storage.AbsolutePath(s.cfg, thumbRel)
	thumbImg := previewImg
	if thumbImg.Bounds().Dx() > thumbMaxWidth {
		thumbImg = imaging.Resize(thumbImg, thumbMaxWidth, 0, imaging.Lanczos)
	}
	thumbFile, err := os.Create(thumbPath)
	if err != nil {
		return nil, err
	}
	if err := imaging.Encode(thumbFile, thumbImg, imaging.JPEG, imaging.JPEGQuality(80)); err != nil {
		thumbFile.Close()
		return nil, err
	}
	if err := thumbFile.Close(); err != nil {
		return nil, err
	}
	thumbInfo, err := os.Stat(thumbPath)
	if err != nil {
		return nil, err
	}

	asset := &models.MediaAsset{
		OriginalPath:  originalRel,
		PreviewPath:   previewRel,
		ThumbPath:     thumbRel,
		MimeType:      mimeType,
		OriginalSize:  uint64(len(content)),
		PreviewSize:   uint64(previewInfo.Size()),
		ThumbSize:     uint64(thumbInfo.Size()),
		Width:         uint(cfg.Width),
		Height:        uint(cfg.Height),
		PreviewWidth:  uint(previewImg.Bounds().Dx()),
		PreviewHeight: uint(previewImg.Bounds().Dy()),
		ThumbWidth:    uint(thumbImg.Bounds().Dx()),
		ThumbHeight:   uint(thumbImg.Bounds().Dy()),
	}

	if err := s.assetRepo.Create(ctx, asset); err != nil {
		return nil, err
	}

	return asset, nil
}

func normalizeMIME(value string) string {
	v := strings.TrimSpace(strings.ToLower(value))
	if idx := strings.Index(v, ";"); idx >= 0 {
		v = v[:idx]
	}
	if v == "image/jpg" {
		return "image/jpeg"
	}
	return v
}

func isAllowedMIME(mime string, allowed []string) bool {
	m := normalizeMIME(mime)
	for _, candidate := range allowed {
		if normalizeMIME(candidate) == m {
			return true
		}
	}
	return false
}
